上一篇提到我發大量請求,目前遇到一些問題:
之前用的發API的方式是在本地端實現,但現在想要用在AWS部署的時候,我上面也要有這些資料,這時候如果用雲端的主機去嘗試這麼大量的發API及儲存,其實速度更慢(因為我是用免費的,設備就會受到限制)。
解決方法
一樣是打API拿資料,但在每次抓到資料回來的時候不直接存到資料庫而是在我本地先存成json檔案,然後在上傳到ec2,再從ec2去解析檔案存入資料庫。
實作:
先打個預防針,小弟個人經驗不是很夠,我相信有很多更有效率更漂亮的寫法或方法,但這是我目前的想法,畢竟這個專案我們也有設定時間,不太可能讓我一直想。
每一次要跑的次數
$batchSize = 100;
總數量
$totalPokemons = 1011;
會將寶可夢存放的陣列
$pokemons = [];
這裡的目的在於,如果我今天因為網路問題還是其他原因中斷,我可以從上次結束的位置開始。
// 讀取最後成功的位置
$lastIndex = file_get_contents('/Users/liweide/laravel/pokemon/database/seeders/index.json') ?: 1;
// 讀取之前已存儲的所有寶可夢數據
$existingPokemons = file_get_contents('/Users/liweide/laravel/pokemon/database/seeders/pokemons.json');
if ($existingPokemons) {
$pokemons = json_decode($existingPokemons, true);
}
for ($i = $lastIndex; $i <= $totalPokemons; $i += $batchSize) {
// 防止超過設定得請求上限還繼續打
$endRange = $i + $batchSize - 1;
if ($endRange > $totalPokemons) {
$endRange = $totalPokemons;
}
// 每次都從上次最後跑完的數字開始
// 一次跑設定的batchSize
for ($j = $i; $j <= $endRange; $j++) {
try {
// ... 原本的 API 請求和數據處理 ...
$response1 = Http::get("https://pokeapi.co/api/v2/pokemon/$j");
$pokemonDetail = $response1->json();
$name = $pokemonDetail['name'];
$photo = $pokemonDetail["sprites"]['front_default'];
$moves = $pokemonDetail['moves'];
$moveNames = [];
// 取出此寶可夢所能學的所有的招式名稱
foreach ($moves as $move) {
$moveNames[] = $move['move']['name'];
}
// 取得進化等級,這在pokemon-species的進化鏈裡面
$pokemonData = Http::get("https://pokeapi.co/api/v2/pokemon-species/$j")->json();
$getEvolutionChain = $pokemonData["evolution_chain"]['url'];
$evolutionChain = Http::get($getEvolutionChain)->json();
$minLevel = $this->getEvolutionMinLevel($evolutionChain, $name);
$pokemons[] = [
'name' => $name,
'photo' => $photo,
'evolution_level' => $minLevel,
'skills' => $moveNames
];
// 每次成功請求一筆資料後,立即保存數據和更新成功的索引
$result1 = file_put_contents('/Users/liweide/laravel/pokemon/database/seeders/pokemons.json', json_encode($pokemons, JSON_PRETTY_PRINT));
$result2 = file_put_contents('/Users/liweide/laravel/pokemon/database/seeders/index.json', $j + 1);
if ($result1 === false || $result2 === false) {
// 寫入失敗,可以在這裡記錄或拋出異常
throw new \Exception("Failed to write to file");
}
} catch (\Exception $e) {
// 處理或記錄異常,然後繼續下一筆
// continue;
error_log('Error on Pokemon ID ' . $j . ': ' . $e->getMessage());
file_put_contents(storage_path('error-log.txt'), 'Error on Pokemon ID ' . $j . ': ' . $e->getMessage() . "\n", FILE_APPEND);
}
}
這個sleep的目的在於,我在一定時間內發送太多請求會被對方擋掉
sleep(5);
}
我這裡主要是用cron
crontab -e
的地方做設定
00 12 01 10,11 * cd /Users/liweide/laravel/pokemon && /opt/homebrew/Cellar/php@8.3/8.3.0/bin/php artisan db:seed --class=RaceSeeder && /Users/liweide/laravel/pokemon/upload_to_ec2.sh >> /dev/null 2>&1
*00 12 01 10,11 ***: 這是定義任務何時運行的部分。
00
: 分鐘(每小時的第00分)12
: 小時(中午12點)01
: 日期(每月的第一天)10,11
: 月份(每年的10月和11月)所以這意味著該任務將在每年的10月1日和11月1日的中午12點運行。
而這個時間是根據系統時間,可以根據指令:
cd /Users/liweide/laravel/pokemon: 設定當前工作目錄為 /Users/liweide/laravel/pokemon
。
/opt/homebrew/Cellar/php@8.3/8.3.0/bin/php artisan db:seed --class=RaceSeeder: 使用指定的 PHP 8.3.0 版本運行 Laravel 的 db:seed
命令,特別是 RaceSeeder
。
我們目前需求設定是只有要更新race的資料,如果有其他資料也要更新就再追加。
/Users/liweide/laravel/pokemon/upload_to_ec2.sh: 在前一個命令執行完畢後,執行 upload_to_ec2.sh
腳本。該腳本的具體內容為上傳我前面儲存的json檔
>> /dev/null 2>&1: 這將會把標準輸出和標準錯誤輸出都重定向到 /dev/null
,意味著任何從這個命令輸出的信息都會被丟棄。
關於定時更新,我還需要把檔案上傳到雲端主機這裡會牽涉到幾種不同的上傳方式,我想留到下一篇分享。